%%% -------------------------------------------------------------------
%%% Author  : vivimice
%%% Description :
%%%
%%% Created : Feb 20, 2012
%%% -------------------------------------------------------------------
-module(tio_states_reader).

%%
%% Include files
%%
-include("telnet.hrl").
-include("keys.hrl").
-include("tchar.hrl").

%%
%% Exported Functions
%%
-export([start_link/2, init/2, loop/2, stop/1]).

%%
%% API Functions
%%
start_link(Holder, InputBuffer) ->
	spawn_link(?MODULE, init, [Holder, InputBuffer]).

init(Holder, InputBuffer) ->
	logger:trap_error(),
	loop(Holder, InputBuffer).

loop(Holder, InputBuffer) ->
	case blocking_binary_queue:take(InputBuffer, 1) of
		Byte when is_integer(Byte) ->
			if
				(Byte band 16#80) > 0 ->
					% mb chars, skip next byte
					blocking_binary_queue:take(InputBuffer, 1),
					tio_states_holder:move_cursor_pos(Holder, {0, 2});
				
				Byte == 16#1b ->
					% escape sequence
					process_escape_sequence(Holder, InputBuffer);
				
				Byte == ?IAC ->
					% command
					skip_command(InputBuffer);
					
				Byte == 16#0d ->
					% new line
					tio_states_holder:new_line(Holder);
				
				Byte == 16#08 orelse Byte == 16#7f ->
					% backspace
					ok;
				
				true ->
					% ansi char
					tio_states_holder:move_cursor_pos(Holder, {0, 1})
			end,
			loop(Holder, InputBuffer);
		
		{error, Reason} ->
			erlang:exit(Reason)
	end.

stop(Reader) ->
	exit(Reader, kill).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% read command option and suboptions - FSMs
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
skip_command(InputBuffer) ->
	blocking_binary_queue:take(InputBuffer, 2).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% read escape sequence - FSMs
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_escape_sequence(Holder, InputBuffer) ->
	loop_escape_sequence(Holder, InputBuffer, [blocking_binary_queue:take(InputBuffer, 1)]).

loop_escape_sequence(Holder, InputBuffer, "[") ->
	case loop_escape_sequence_parameters(InputBuffer, [], []) of
		
		% cursor up one row
		{args, [], command, $A} ->
			tio_states_holder:move_cursor_pos(Holder, {-1, 0});
		{args, [NumRows], command, $A} ->
			tio_states_holder:move_cursor_pos(Holder, {-NumRows, 0});
		
		% cursor down one row
		{args, [], command, $B} ->
			tio_states_holder:move_cursor_pos(Holder, {1, 0});
		{args, [NumRows], command, $B} ->
			tio_states_holder:move_cursor_pos(Holder, {NumRows, 0});
		
		% cursor right one column
		{args, [], command, $C} ->
			tio_states_holder:move_cursor_pos(Holder, {0, 1});
		{args, [NumColumns], command, $C} ->
			tio_states_holder:move_cursor_pos(Holder, {0, NumColumns});
		
		% cursor left one column
		{args, [], command, $D} ->
			tio_states_holder:move_cursor_pos(Holder, {0, -1});
		{args, [NumColumns], command, $D} ->
			tio_states_holder:move_cursor_pos(Holder, {0, -NumColumns});
		
		% set cursor absolute position
		{args, Args, command, Command} 
		  when (Command == $H orelse Command == $f),
			   (Args == [] orelse Args == [0, 0]) ->
			tio_states_holder:set_cursor_pos(Holder, {1, 1});
		{args, [Row, Column], command, Command}
		  when (Command == $H orelse Command == $f) ->
			tio_states_holder:set_cursor_pos(Holder, {Row, Column});
			
		% set character attributes
		{args, Args, command, $m} ->
			loop_set_character_attributes(Holder, Args);
		
		Other ->
			logger:warn("Unknown escape sequence: ~w", [Other])
	end;

loop_escape_sequence(_, _, _) ->
	% undefined escape sequence
	undef.

loop_escape_sequence_parameters(InputBuffer, Args, CurrentArg) ->
	case blocking_binary_queue:take(InputBuffer, 1) of
		Byte when Byte >= $0 andalso Byte =< $9 ->
			loop_escape_sequence_parameters(InputBuffer, Args, CurrentArg ++ [Byte]);
		
		$; when CurrentArg == [] ->
			loop_escape_sequence_parameters(InputBuffer, Args ++ [0], []);
		
		$; ->
			loop_escape_sequence_parameters(InputBuffer, Args ++ [list_to_integer(CurrentArg)], []);
		
		Command ->
			{args, Args, command, Command}
	end.

loop_set_character_attributes(_, []) ->
	ok;
loop_set_character_attributes(Holder, [Head|Rest]) ->
	case Head of
		0 ->
			tio_states_holder:clear_character_attributes(Holder);
		1 ->
			tio_states_holder:set_bold(Holder, true);
		2 ->
			tio_states_holder:set_dim(Holder, true);
		4 ->
			tio_states_holder:set_underline(Holder, true);
		5 ->
			tio_states_holder:set_blink(Holder, true);
		7 ->
			tio_states_holder:set_inverse(Holder, true);
		8 ->
			tio_states_holder:set_hidden(Holder, true);
		ColorIndex when ColorIndex >= 30, ColorIndex =< 37 ->
			tio_states_holder:set_foreground(Holder, ColorIndex - 30);
		ColorIndex when ColorIndex >= 40, ColorIndex =< 47 ->
			tio_states_holder:set_foreground(Holder, ColorIndex - 40);
		Other ->
			logger:warn("Unidentified character attribute number: ~p", [Other])
	end,
	loop_set_character_attributes(Holder, Rest).
